﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace HierarchicalClustersGenerator
{
    class HierarchialClustersGenerator
    {
        const int DEFAULT_MIN_DOCS = 10;

        public int MinDocsPerCluster { get; set; }

        private ClustersGenerator cg;
        private Dictionary<string, List<string>> m_clusters;  // would hold the results, see GenerateHierarchialClusters function
        Dictionary<string, List<double>> m_docs; // the docs to be classified. key = doc name, value = list of it's tf-idf
        private List<int> m_features; // the features (n-grams)

        // default constructor
        public HierarchialClustersGenerator()
        {
            MinDocsPerCluster = DEFAULT_MIN_DOCS;
            cg = new ClustersGenerator();
            cg.NumOfClusters = 5;
            cg.Seed = 10;
            m_clusters = new Dictionary<string, List<string>>(); // key = cluster name, value = list of docs names that belongs to the cluster
            m_docs = new Dictionary<string, List<double>>(); // key = doc name, value = list of tf-idf weights
        }

        /// <summary>
        /// Recieving a dict of documents(key = doc name, value= document) and generating hierarchial clusters. returns a dictionary, 
        /// where the key is the cluster name, in the format: #_#_#_....# , For example, 2_4_1 means second cluster 
        /// in first layer, 4rth in the second etc.  
        /// </summary>
        public Dictionary<string, List<string>> GenerateHierarchialClusters(Dictionary<string,Document> documents)
        {
  
            m_docs = this.MakeDocsCGFormat(documents);  // creating docs and features for the cg
   
            foreach (KeyValuePair<string, Document> doc in documents)  // initializing m_features
            {
                m_features = this.GetFeatures(doc.Value);
                break;
            }

            Dictionary<int, List<string>> clusters = cg.GenerateClusters(m_docs, m_features); // creating first layer of clusters 

            // adding first cluster partition to results
            foreach (KeyValuePair<int, List<string>> cls in clusters)
            {
                m_clusters.Add(cls.Key.ToString(), cls.Value);
            }

            Dictionary<string, List<string>> m_clusters_copy = new Dictionary<string,List<string>>( m_clusters);

            // start of iterative sub-clusters partition
            foreach (KeyValuePair<string, List<string>> cls in m_clusters_copy)
            {
                if (!ExtractNode(cls.Key))  // if sub-cluster can't be partitioned anymore
                    continue;
            }

            return m_clusters;
        }

        private bool ExtractNode(string cluster)
        {

            Dictionary<string, List<double>> docs = GetClusterDocs(cluster);  // all the documents in this cluster
            Dictionary<int, List<string>> newClusters = cg.GenerateClusters(docs, m_features);  // generating new sub-clusters

            //if (docs.Count == 5)
            //    cg.Seed = 10;

            if (docs.Count < MinDocsPerCluster || newClusters.Count == 1)
                return false;


            else
            {
                

                //if (!CheckClustering(newClusters))
                //    return false;


                UpdateClusters(cluster, newClusters);        //update clusters
                string subClusterName;
                for (int i = 1; i <= newClusters.Count; i++)  //Extract recursivly sub clusters.
                {
                    subClusterName = cluster + "_" + i.ToString();
                    if (!ExtractNode(subClusterName))
                        continue;
                }
                return true;
            }
        }

        private void UpdateClusters(string oldCluster, Dictionary<int, List<string>> newClusters)
        {
            string newClusterName;
            for (int i = 1; i <= newClusters.Count; i++)
            {
                newClusterName = oldCluster + "_" + i.ToString();
                m_clusters.Remove(oldCluster);
                m_clusters.Add(newClusterName, newClusters[i]);

            }
        }


        ///// <summary>
        ///// 
        ///// </summary>
        //private Dictionary<string, List<Document>> GetClusters(Dictionary<int, List<string>> clusters, List<Document> docs)
        //{
        //    // TODO: return clusters in whole document format
        //    return null;
        //}


        /// <summary>
        /// making the list of documents in the ClustersGenerator format
        /// </summary>
        private Dictionary<string, List<double>> MakeDocsCGFormat(Dictionary<string, Document> docs)
        {
            Dictionary<string, List<double>> toReturn = new Dictionary<string, List<double>>();
            foreach (KeyValuePair<string,Document> doc in docs)
            {
                toReturn.Add(doc.Key, doc.Value.tfIdfList);
            }

            return toReturn;
        }

        /// <summary>
        /// making and returning a list of features -no importance to their number, only the size of the list  
        /// </summary>
        private List<int> GetFeatures(Document d)
        {
            List<int> toReturn = new List<int>();
            for (int i = 0; i < d.tfIdfList.Count; i++)
                toReturn.Add(i);


            return toReturn;
        }


        /// <summary>
        /// checking if partition to clusters stands the contition of min docs per cluster
        /// </summary>
        private bool CheckClustering(Dictionary<int, List<string>> clusters)
        {
            foreach (KeyValuePair<int,List<string>> cluster in clusters)
            {
                if (cluster.Value.Count < MinDocsPerCluster)
                    return false;
            }

            return true;
        }



        private Dictionary<string, List<double>> GetClusterDocs(string cluster)
        {
            Dictionary<string, List<double>> toReturn = new Dictionary<string, List<double>>();

            foreach (string doc in m_clusters[cluster]) // for each document(name) in the cluster
            {
                toReturn.Add(doc, m_docs[doc]);  // adding document info
            }

            return toReturn;
        }

    }
}
